/** Bombs-Must-Detonate: Parser Generator **/

%token ENDOFFILE
%token <string> IDENTIFIER
%token <string> STR
%token INCLUDE
%token INT FLOAT STRING BOOL VOID
%token ENUM STRUCT LIST ARRAY
%token REF
%token REMOTABLE
%token LPAREN RPAREN LBRACKET RBRACKET LT GT LCURLBRACKET RCURLBRACKET
%token QUOTES COMMA PERIOD SEMICOLON COLON EXCLAMATION TILDE DOLLAR
%token AMPER PIPE
%token <int> INTVAL
%token <float> FLOATVAL
%token TRUE FALSE
%token NIL
%token EQUALS
%token IF ELSE WHILE FOR BREAK CONTINUE DO
%token CAR CDR TRUNC NOT NULL
%token AND OR NOR XOR NAND ADD SUB MUL DIV IDIV MOD CONCAT
%token BREAK CONTINUE
%token RETURN
%token TEMPLATE
%token STATEMACHINE STATE FSMINIT TRANSITION PREDICATE TARGET

%left REMOTABLE
%left RETURN BREAK CONTINUE INCLUDE SEMICOLON LCURLBRACKET RCURLBRACKET
%left ELSE 
%left IF WHILE FOR DO
%left CAR CDR NULL
%left NOT TILDE
%left TRUNC 
%left LBRACKET RBRACKET
%left LIST
%left ARRAY
%left REF
%left ENUM STRUCT
%left INT FLOAT STRING BOOL VOID
%left EQUALS
%left EXCLAMATION
%right AND OR NOR XOR AND AMPER PIPE
%left CONCAT COLON DOLLAR
%left LT GT
%left ADD SUB
%left MUL DIV IDIV MOD
%left QUOTES COMMA PERIOD
%left TRUE FALSE NIL INTVAL FLOATVAL IDENTIFIER STR
%left LPAREN RPAREN
%left LPRI
%left MPRI
%left HPRI

%start program
%type <Ast.src_program> program

%%

program:   ENDOFFILE                         { [] }
         | global_expression program         { $1::$2 }

global_expression: function_declaration SEMICOLON
                                             { $1 }
	 | function_definition               { $1 }
	 | struct_declaration                { $1 }
         | global_enum_declaration           { $1 }
         | global_var_declare                { $1 }
         | INCLUDE QUOTES IDENTIFIER QUOTES  { Ast.SynInclude (ref (Ast.IncludeFileName $3)) }
	 | INCLUDE STR                       { Ast.SynInclude (ref (Ast.IncludeFileName $2)) }
	 | fsm_state_machine                 { Ast.SynStateMachine $1 }

/* Functions */
function_declaration: data_type IDENTIFIER LPAREN arg_list RPAREN
                                             { Ast.SynFunctionDeclare ($1,$2,$4) }
	 | REMOTABLE data_type IDENTIFIER LPAREN arg_list RPAREN
	                                     { Ast.SynRemotableFunctionDeclare ($2,$3,$5) }
	 | TEMPLATE LT identifier_list GT function_declaration
	                                     { Ast.SynTemplatedDeclare ($3,$5) }
function_definition: function_declaration LCURLBRACKET function_body RCURLBRACKET 
                                             { match $1 with 
						   Ast.SynFunctionDeclare (a,b,c) ->
						     Ast.SynFunctionDefine ((a,b,c),$3)
						 | Ast.SynTemplatedDeclare (lst,Ast.SynFunctionDeclare (a,b,c)) ->
						     Ast.SynTemplatedDefine (lst,Ast.SynFunctionDefine ((a,b,c),$3))
						 | _ -> raise (Failure "Remotable keyword should be used in function declaration, not in function definition.") }

function_body: /* empty */                   { [] }
	 | expression function_body          { $1::$2 }
arg_list:  /* empty */                       { [] } 
         | arg_list_nonempty                 { $1 }
arg_list_nonempty: data_type IDENTIFIER COMMA arg_list_nonempty
                                             { ($1,$2)::$4 }
	 | data_type IDENTIFIER              { ($1,$2)::[] }
function_call: IDENTIFIER LPAREN value_producer_list RPAREN
                                             { Ast.SynLocalCall ($1,$3,ref false) }
         | EXCLAMATION LPAREN IDENTIFIER LPAREN value_producer_list RPAREN COMMA var_ident RPAREN
                                             { Ast.SynRemoteCall ($3,$5,$8) }
	 | EXCLAMATION LPAREN IDENTIFIER LPAREN value_producer_list RPAREN RPAREN
	                                     { Ast.SynRemoteCallNoResult ($3,$5) }

/* Structs & Enums */
struct_declaration: STRUCT IDENTIFIER LCURLBRACKET struct_list RCURLBRACKET SEMICOLON
                                             { Ast.SynStructDeclare ($2,$4) }
global_enum_declaration: ENUM IDENTIFIER LCURLBRACKET identifier_list RCURLBRACKET SEMICOLON
                                             { Ast.SynGlobalEnumDeclare ($2,$4) }

enum_declaration: ENUM IDENTIFIER LCURLBRACKET identifier_list RCURLBRACKET
					     { Ast.SynEnumDeclare ($2,$4) }

struct_list: data_type IDENTIFIER SEMICOLON struct_list
                                             { (Ast.SynVarDeclareNoInit ($1,$2))::$4 }
         | data_type IDENTIFIER SEMICOLON    { (Ast.SynVarDeclareNoInit ($1,$2))::[] }
        
identifier_list: IDENTIFIER COMMA identifier_list        { $1::$3 }
         | IDENTIFIER                        { $1::[] }

/* Global Var Declare */
global_var_declare: data_type IDENTIFIER SEMICOLON
                                             { Ast.SynGlobalVarDeclare (Ast.SynVarDeclareNoInit ($1,$2)) }

/* Data Types */
data_type: INT                               { Ast.SynIntType }
	 | FLOAT                             { Ast.SynFloatType }
	 | STRING                            { Ast.SynStringType }
         | BOOL                              { Ast.SynBoolType }
         | VOID                              { Ast.SynVoidType }
	 | IDENTIFIER                        { Ast.SynEnumOrStructType ($1,ref []) }
	 | data_type ARRAY LBRACKET value_producer RBRACKET
                                             { Ast.SynArrayType ($1,$4) }
	 | data_type ARRAY LBRACKET RBRACKET       
                                             { Ast.SynArrayType ($1,Ast.SynValue (Ast.SynIntValue 0)) }
         | data_type LIST                    { Ast.SynListType $1 }
         | data_type REF                     { Ast.SynRefType $1 }
	 | LPAREN data_type_list RPAREN SUB GT data_type
	                                     { Ast.SynArrowType ($2,$6) }

data_type_list: data_type                    { [$1] }
	 | data_type COMMA data_type_list    { $1::$3 }

/* Values */
var_ident: IDENTIFIER                        { Ast.SynVarName $1 }
	 | var_ident PERIOD IDENTIFIER       { Ast.SynStructOrEnumValue ($1,$3,ref Ast.VIdUnspecified) }
	 | var_ident LBRACKET value_producer RBRACKET
	                                     { Ast.SynArrayCell ($1,$3) }

value_producer: 
	 | value                             { Ast.SynValue $1 }
	 | function_call                     { Ast.SynFunctionCallValue $1 }
	 | var_ident                         { Ast.SynVarIdentifier $1 } %prec LPRI
	 | value_producer binop value_producer
                                             { Ast.SynBinop ($1,$2,$3) } %prec MPRI
         | pre_unop value_producer           { Ast.SynPrefixUnop ($1,$2) } %prec HPRI
         | LPAREN value_producer RPAREN      { Ast.SynParenthesized $2 } %prec HPRI
	 | LCURLBRACKET array_value_producer_list RCURLBRACKET
	                                     { Ast.SynArrayValueProducer $2 }
	 | list_value_producer               { Ast.SynListValueProducer $1 }

value:     INTVAL                            { Ast.SynIntValue $1 }
	 | FLOATVAL                          { Ast.SynFloatValue $1 }
         | STR                               { Ast.SynStringValue $1 }
	 | QUOTES IDENTIFIER QUOTES          { Ast.SynStringValue $2 }
	 | TRUE                              { Ast.SynBoolValue true }
	 | FALSE                             { Ast.SynBoolValue false }

value_producer_list: /* empty */             { [] }
	 | value_producer_list_nonempty      { $1 }
value_producer_list_nonempty: value_producer COMMA value_producer_list_nonempty
                                             { $1::$3 }
	 | value_producer                    { $1::[] } %prec MPRI

list_value_producer_list: value_producer SEMICOLON list_value_producer_list
                                             { $1::$3 }
	 | value_producer                    { $1::[] } %prec MPRI

                    
array_value_producer_list: value_producer COMMA array_value_producer_list
                                             { $1::$3 }
	 | value_producer                    { $1::[] } %prec MPRI


list_value_producer: NIL LBRACKET data_type RBRACKET
                                             { Ast.SynListNil $3 }
	 | LBRACKET  list_value_producer_list RBRACKET
                                             { Ast.SynListList $2 }
	 | value_producer COLON COLON value_producer
	                                     { Ast.SynListCons ($1,$4) }
                                             
/* Comparisons and Operators */

pre_unop:  NOT                               { Ast.SynUnopNot }
	 | TILDE                             { Ast.SynUnopNot }
	 | CAR                               { Ast.SynUnopCar }
	 | CDR                               { Ast.SynUnopCdr }
	 | TRUNC                             { Ast.SynUnopTrunc }
	 | DOLLAR                            { Ast.SynUnopDeref }
	 | SUB                               { Ast.SynUnopNeg }
	 | NULL                              { Ast.SynUnopNull }

binop:     AND                               { Ast.SynBinopAnd }
	 | OR                                { Ast.SynBinopOr }
	 | ADD                               { Ast.SynBinopAdd }
	 | SUB                               { Ast.SynBinopSub }
	 | MUL                               { Ast.SynBinopMul }
	 | DIV                               { Ast.SynBinopDiv }
	 | IDIV                              { Ast.SynBinopIDiv }
	 | MOD                               { Ast.SynBinopMod }
	 | CONCAT                            { Ast.SynBinopConcat }
	 | COLON COLON                       { Ast.SynBinopCons }
         | LT EQUALS                         { Ast.SynCompLte } %prec HPRI
	 | GT EQUALS                         { Ast.SynCompGte } %prec HPRI
	 | LT                                { Ast.SynCompLt } %prec MPRI
         | GT                                { Ast.SynCompGt } %prec MPRI
	 | EQUALS                            { Ast.SynCompEq } %prec MPRI
         | EXCLAMATION EQUALS                { Ast.SynCompNeq }

/* Expressions */        
expression: var_declaration SEMICOLON        { $1 }
	 | enum_declaration SEMICOLON        { $1 }
	 | var_assign SEMICOLON              { Ast.SynVarAssign $1 }
	 | conditional                       { Ast.SynCond $1 }
	 | loop                              { Ast.SynLoop $1 }
	 | function_call SEMICOLON           { Ast.SynFunctionCall $1 }
	 | return_statement SEMICOLON        { $1 }
	 | BREAK SEMICOLON                   { Ast.SynBreak }
	 | CONTINUE SEMICOLON                { Ast.SynContinue }

expression_list: /* empty */                 { [] }
	 | expression_list_nonempty          { $1 }
expression_list_nonempty: expression expression_list_nonempty
                                             { $1::$2 }
	 | expression                        { $1::[] }

var_declaration: data_type IDENTIFIER        { Ast.SynVarDeclare (Ast.SynVarDeclareNoInit ($1,$2)) }
         | data_type IDENTIFIER EQUALS value_producer
                                             { Ast.SynVarDeclare (Ast.SynVarDeclareWithInit ($1,$2,$4)) }
var_assign: var_ident EQUALS value_producer
                                             { Ast.SynVarAssignment ($1,$3) }
	 | var_ident binop EQUALS value_producer
                                             { Ast.SynVarModify ($1,$2,$4) }

/* Conditionals */
conditional: IF value_producer LCURLBRACKET expression_list RCURLBRACKET
                                             { Ast.SynIf ($2,$4) }
         | IF value_producer LCURLBRACKET expression_list RCURLBRACKET continued_conditional
                                             { Ast.SynIfCase ($2,$4,$6) }
continued_conditional: ELSE LCURLBRACKET expression_list RCURLBRACKET
                                             { Ast.SynFinalElse $3 }
         | ELSE conditional                  { Ast.SynElse $2 }

/* Loops */
loop:      WHILE value_producer LCURLBRACKET expression_list RCURLBRACKET
                                             { Ast.SynWhile ($2,$4) }
         | FOR LPAREN expression value_producer SEMICOLON expression RPAREN LCURLBRACKET expression_list RCURLBRACKET
                                             { Ast.SynFor ($3,$4,$6,$9) }
         | DO LCURLBRACKET expression_list RCURLBRACKET WHILE value_producer SEMICOLON
                                             { Ast.SynDoWhile ($3,$6) }

return_statement: RETURN                     { Ast.SynReturnStatement (Ast.SynVoidReturn) }
	 | RETURN value_producer             { Ast.SynReturnStatement (Ast.SynValueReturn $2) }

/* State Machine Syntax */

fsm_state_machine: STATEMACHINE IDENTIFIER LCURLBRACKET fsm_init fsm_state_list RCURLBRACKET
                                             { ($2,$4,$5) }
fsm_state: STATE IDENTIFIER LCURLBRACKET fsm_callback_list fsm_transition_list RCURLBRACKET 
                                             { ($2,$4,$5) }
fsm_state_list: fsm_state                    { [$1] }
	 | fsm_state fsm_state_list          { $1::$2 }
fsm_callback: IDENTIFIER COLON IDENTIFIER SEMICOLON
                                             { match $1 with
						   "onInit" -> Ast.SynFsmOnInit $3
						 | "onMoveRequest" -> Ast.SynFsmOnMoveRequest $3
						 | "onTeammateDeath" -> Ast.SynFsmOnTeammateDeath $3
						 | "onBombDetonate" -> Ast.SynFsmOnBombDetonate $3
						 | "onDeath" -> Ast.SynFsmOnDeath $3
						 | _ -> raise Parse_error }
fsm_callback_list: /* empty */               { [] }
	 | fsm_callback_list_nonempty        { $1 }
fsm_callback_list_nonempty: fsm_callback     { [$1] }
	 | fsm_callback fsm_callback_list_nonempty
                                             { $1::$2 }


fsm_init: FSMINIT COLON IDENTIFIER SEMICOLON { $3 }

fsm_transition: TRANSITION LCURLBRACKET PREDICATE COLON identifier_list SEMICOLON TARGET COLON fsm_transition_target_list SEMICOLON RCURLBRACKET
                                             { ($5,$9) }
fsm_transition_list: /* empty */             { [] }
	 | fsm_transition_list_nonempty      { $1 }
fsm_transition_list_nonempty: fsm_transition { [$1] }
	 | fsm_transition fsm_transition_list_nonempty
                                             { $1::$2 }

fsm_transition_target: IDENTIFIER FLOATVAL   { if $2 <= 0.0 then raise Parse_error else ($1,$2) }
fsm_transition_target_list: /* empty */      { [] }
	 | fsm_transition_target_list_nonempty
                                             { $1 }
fsm_transition_target_list_nonempty: fsm_transition_target 
                                             { [$1] }
	 | fsm_transition_target COMMA fsm_transition_target_list_nonempty
                                             { $1::$3 }


